package com.ysstech.common.encryption;


import com.ysstech.common.encryption.sm2.Cipher;
import com.ysstech.common.encryption.sm2.SM2;
import com.ysstech.common.encryption.sm2.SM2KeyVO;
import com.ysstech.common.encryption.sm2.SM2SignVO;
import com.ysstech.common.encryption.sm2.SM2SignVerUtils;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;

import java.io.IOException;
import java.math.BigInteger;
@Slf4j
public class SM2Utils {

    private final static String publicKey = "04A03AA1FC6D51F06F1717916A3D541CE5B441CD7A9378E76A3CE6D386A8D357D9BAE8EE008BC2C10CC57E929FE273280808737DFE7A5636D118821E272D655D8B";
    private final static String privatekey = "E946BA9A535AFA0F204BEBF47B0C83E555D7C382797C3F1635948E38138A0127";

    //生成随机秘钥对
    public static SM2KeyVO generateKeyPair() {
        SM2 sm2 = SM2.Instance();
        AsymmetricCipherKeyPair key = null;
        while (true) {
            key = sm2.ecc_key_pair_generator.generateKeyPair();
            if (((ECPrivateKeyParameters) key.getPrivate()).getD().toByteArray().length == 32) {
                break;
            }
        }
        ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
        ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
        BigInteger privateKey = ecpriv.getD();
        ECPoint publicKey = ecpub.getQ();
        SM2KeyVO sm2KeyVO = new SM2KeyVO();
        sm2KeyVO.setPublicKey(publicKey);
        sm2KeyVO.setPrivateKey(privateKey);
        //log.info("公钥: " + Util.byteToHex(publicKey.getEncoded()));
        //log.info("私钥: " + Util.byteToHex(privateKey.toByteArray()));
        return sm2KeyVO;
    }

    //数据加密
    public static String encrypt(byte[] publicKey, byte[] data) throws IOException {
        if (publicKey == null || publicKey.length == 0) {
            return null;
        }

        if (data == null || data.length == 0) {
            return null;
        }

        byte[] source = new byte[data.length];
        System.arraycopy(data, 0, source, 0, data.length);

        Cipher cipher = new Cipher();
        SM2 sm2 = SM2.Instance();
        ECPoint userKey = sm2.ecc_curve.decodePoint(publicKey);

        ECPoint c1 = cipher.Init_enc(sm2, userKey);
        cipher.Encrypt(source);
        byte[] c3 = new byte[32];
        cipher.Dofinal(c3);

//      log.info("C1 " + Util.byteToHex(c1.getEncoded()));
//      log.infon("C2 " + Util.byteToHex(source));
//      log.info("C3 " + Util.byteToHex(c3));
        //C1 C2 C3拼装成加密字串
        // C1 | C2 | C3
        //return Util.byteToHex(c1.getEncoded()) + Util.byteToHex(source) + Util.byteToHex(c3);
        // C1 | C3 | C2
        return Util.byteToHex(c1.getEncoded()) + Util.byteToHex(c3) + Util.byteToHex(source);
    }

    //数据解密
    public static byte[] decrypt(byte[] privateKey, byte[] encryptedData) throws IOException {
        if (privateKey == null || privateKey.length == 0) {
            return null;
        }

        if (encryptedData == null || encryptedData.length == 0) {
            return null;
        }
        //加密字节数组转换为十六进制的字符串 长度变为encryptedData.length * 2
        String data = Util.byteToHex(encryptedData);
        /***分解加密字串 C1 | C2 | C3
         * （C1 = C1标志位2位 + C1实体部分128位 = 130）
         * （C3 = C3实体部分64位  = 64）
         * （C2 = encryptedData.length * 2 - C1长度  - C2长度）

         byte[] c1Bytes = Util.hexToByte(data.substring(0,130));
         int c2Len = encryptedData.length - 97;
         byte[] c2 = Util.hexToByte(data.substring(130,130 + 2 * c2Len));
         byte[] c3 = Util.hexToByte(data.substring(130 + 2 * c2Len,194 + 2 * c2Len));
         */
        /***分解加密字串 C1 | C3 | C2
         * （C1 = C1标志位2位 + C1实体部分128位 = 130）
         * （C3 = C3实体部分64位  = 64）
         * （C2 = encryptedData.length * 2 - C1长度  - C2长度）
         */
        byte[] c1Bytes = Util.hexToByte(data.substring(0, 130));
        int c2Len = encryptedData.length - 97;
        byte[] c3 = Util.hexToByte(data.substring(130, 130 + 64));
        byte[] c2 = Util.hexToByte(data.substring(194, 194 + 2 * c2Len));

        SM2 sm2 = SM2.Instance();
        BigInteger userD = new BigInteger(1, privateKey);

        //通过C1实体字节来生成ECPoint
        ECPoint c1 = sm2.ecc_curve.decodePoint(c1Bytes);
        Cipher cipher = new Cipher();
        cipher.Init_dec(userD, c1);
        cipher.Decrypt(c2);
        cipher.Dofinal(c3);

        //返回解密结果
        return c2;
    }

    //公钥加密
    public static String SM2Enc(String pubKey, String src) throws IOException {
        String encrypt = SM2Utils.encrypt(Util.hexStringToBytes(pubKey), src.getBytes());
        //删除04
        encrypt = encrypt.substring(2, encrypt.length());
        return encrypt;
    }

    //私钥解密
    public static String SM2Dec(String priKey, String encryptedData) throws IOException {
        //填充04
        encryptedData = "04" + encryptedData;
        byte[] decrypt = SM2Utils.decrypt(Util.hexStringToBytes(priKey), Util.hexStringToBytes(encryptedData));
        return new String(decrypt);
    }

    //私钥签名,参数二:原串必须是hex!!!!因为是直接用于计算签名的,可能是SM3串,也可能是普通串转Hex
    public static SM2SignVO genSM2Signature(String priKey, String sourceData) throws Exception {
        SM2SignVO sign = SM2SignVerUtils.Sign2SM2(Util.hexToByte(priKey), Util.hexToByte(sourceData));
        return sign;
    }

    //公钥验签,参数二:原串必须是hex!!!!因为是直接用于计算签名的,可能是SM3串,也可能是普通串转Hex
    public static boolean verifySM2Signature(String pubKey, String sourceData, String hardSign) {
        SM2SignVO verify = SM2SignVerUtils.VerifySignSM2(Util.hexStringToBytes(pubKey), Util.hexToByte(sourceData), Util.hexToByte(hardSign));
        return verify.isVerify();
    }

    //SM2签名Hard转soft
    public static String SM2SignHardToSoft(String hardSign) {
        byte[] bytes = Util.hexToByte(hardSign);
        byte[] r = new byte[bytes.length / 2];
        byte[] s = new byte[bytes.length / 2];
        System.arraycopy(bytes, 0, r, 0, bytes.length / 2);
        System.arraycopy(bytes, bytes.length / 2, s, 0, bytes.length / 2);
        ASN1Integer d_r = new ASN1Integer(Util.byteConvertInteger(r));
        ASN1Integer d_s = new ASN1Integer(Util.byteConvertInteger(s));
        ASN1EncodableVector v2 = new ASN1EncodableVector();
        v2.add(d_r);
        v2.add(d_s);
        DERSequence sign = new DERSequence(v2);

        String result = null;
        try {
            result = Util.byteToHex(sign.getEncoded());
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        //SM2加密机转软加密编码格式
        //return SM2SignHardKeyHead+hardSign.substring(0, hardSign.length()/2)+SM2SignHardKeyMid+hardSign.substring(hardSign.length()/2);
        return result;
    }

    //加密
    private static String sm2encrypt(String decryptData) throws IOException {
        String encrypt = SM2Utils.encrypt(Util.hexStringToBytes(publicKey), decryptData.getBytes());
        //删除04
        encrypt = encrypt.substring(2, encrypt.length());
        return encrypt.toUpperCase();
    }

    //解密
    private static String sm2decrypt(String encryptedData) throws IOException {
        //填充04
        encryptedData = "04" + encryptedData;
        byte[] decrypt = SM2Utils.decrypt(Util.hexStringToBytes(privatekey), Util.hexStringToBytes(encryptedData));
        return new String(decrypt);
    }

    //软件加密方式验签
    private static boolean sm2signForSoft(String src) throws Exception {
        SM2SignVO sign = genSM2Signature(privatekey, Util.byteToHex(src.getBytes()));
        return  verifySM2Signature(publicKey, Util.byteToHex(src.getBytes()), sign.getSm2_signForSoft());
    }
    //硬件加密方式验签
    private static boolean sm2signForHard(String src) throws Exception {
        SM2SignVO sign = genSM2Signature(privatekey, Util.byteToHex(src.getBytes()));
        return  verifySM2Signature(publicKey, Util.byteToHex(src.getBytes()), SM2SignHardToSoft(sign.getSm2_signForHard()));
    }

    public static void main(String[] args) throws Exception {

        log.info("--产生SM2秘钥--:");
   /*     SM2KeyVO sm2KeyVO = SM2Utils.generateKeyPair();
        log.info("公钥:" + sm2KeyVO.getPubHexInSoft());
        log.info("私钥:" + sm2KeyVO.getPriHexInSoft());*/
        //数据加密
        log.info("--测试加密开始--");
        String src = "123456";
        log.info("原文UTF-8转hex:" + Util.byteToHex(src.getBytes()));
        String SM2Enc = sm2encrypt(src);
        log.info("加密:");
        log.info("密文:" + SM2Enc);
        String SM2Dec = sm2decrypt("306F0220358E13147A691EA331191943A6098156CB6DB9251B8ED2C2145B135F2B2ED95B022100E4BE3A2D9BFB18BCD8C09CA32B0382A8CDA2184E50F0AA9E48593569B154041D0420700294779C937A0DF3AAA174AEACF8E4096AB024DA71C712298961DB0557868A04068C82F762A503");
        log.info("解密:" + SM2Dec);
        log.info("--测试加密结束--");
/*
        log.info("--测试SM2签名--");
        log.info("原文hex:" + Util.byteToHex(src.getBytes()));
        String s5 = Util.byteToHex(src.getBytes());

        log.info("签名测试开始:");
        SM2SignVO sign = genSM2Signature(sm2KeyVO.getPriHexInSoft(), s5);
        log.info("软加密签名结果:" + sign.getSm2_signForSoft());
        log.info("加密机签名结果:" + sign.getSm2_signForHard());
        //log.info("转签名测试:"+SM2SignHardToSoft(sign.getSm2_signForHard()));
        log.info("验签1,软件加密方式:");
        boolean b = verifySM2Signature(sm2KeyVO.getPubHexInSoft(), s5, sign.getSm2_signForSoft());
        log.info("软件加密方式验签结果:" + b);
        log.info("验签2,硬件加密方式:");
        String sm2_signForHard = sign.getSm2_signForHard();
        log.info("签名R:"+sign.sign_r);
        log.info("签名S:"+sign.sign_s);
        //log.info("硬:"+sm2_signForHard);
        b = verifySM2Signature(sm2KeyVO.getPubHexInSoft(), s5, SM2SignHardToSoft(sign.getSm2_signForHard()));
        log.info("硬件加密方式验签结果:" + b);
        if (!b) {
            throw new RuntimeException();
        }
        log.info("--签名测试结束--");*/
    }
}
